/**
 * @file xcd_x11.c
 * @brief X11 interface functions.
 * @author Guo-Rong Koh
 * @date 24 February 2008
 *
 * @par Copyright Notice
 * Copyright Guo-Rong Koh 2008.
 */

/*
 * This file is part of xceed.
 *
 * xceed is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License Version 2 as published by the Free
 * Software Foundation.
 *
 * xceed is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * xceed.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"
#include "xcd_log.h"
#include "xcd_x11.h"
#include "xcd_util.h"

#include <X11/extensions/Xinerama.h>

#if XCD_USE_NVCTRL
#include <NVCtrl/NVCtrlLib.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

static bool error_wm = false;

/**
 * Handle the BadAccess error when another window manager is already registered.
 *
 * @param[in] display     Display.
 * @param[in] error_event Error event.
 */
int xcd_x11_error_wm(Display *display, XErrorEvent *error_event)
{
    if (error_event->error_code == BadAccess)
        error_wm = true;

    return 0;
}

Display *xcd_x11_init(void)
{
    Display *display = NULL;

    display = XOpenDisplay(NULL);
    if (display == NULL)
    {
        xcd_logf(XCD_LOG_ERROR, "Cannot connect to X server \"%s\"\n",
                 XDisplayName(NULL));
        return NULL;
    }

    /* Attempt to register as the window manager. */
    XSetErrorHandler(xcd_x11_error_wm);
    XSelectInput(display, DefaultRootWindow(display), SubstructureRedirectMask);
    XSync(display, False);

    if (error_wm)
    {
        xcd_logf(XCD_LOG_ERROR, "Other window manager already registered\n");
        XSetErrorHandler(NULL);
        XSync(display, False);
        xcd_x11_exit(display);
        return NULL;
    }

    return display;
}

void xcd_x11_exit(Display *display)
{
    XFlush(display);
    XCloseDisplay(display);
}

xcd_head_t *xcd_x11_get_heads(Display *display, int *count)
{
    xcd_head_t *heads    = NULL;
    int         detected = 0;
    int         i        = 0;

    /* nVidia's dynamic TwinView doesn't seem to update its Xinerama hints
     * correctly. Use the NVCtrl library directly if available. */
#if XCD_USE_NVCTRL

    Bool success         = False;
    int  enabled_display = 1;
    int  enabled         = 0;

    success = XNVCTRLQueryAttribute(display,
                                    DefaultScreen(display),
                                    0, /* no display_mask */
                                    NV_CTRL_ENABLED_DISPLAYS,
                                    &enabled);
    if (success != True)
        return NULL;

    detected = xcd_util_popcount32(enabled);

#else

    XineramaScreenInfo *screens = NULL;

    screens = XineramaQueryScreens(display, &detected);

    if (screens == NULL)
        return NULL;

#endif

    heads = malloc(detected * sizeof(xcd_head_t));

#if XCD_USE_NVCTRL

    for (; enabled_display > 0 && i < detected; enabled_display <<= 1)
    {
        int  query_display = enabled_display & enabled;
        int  resolution    = 0;

        if (query_display)
        {
            success = XNVCTRLQueryAttribute(display,
                                            DefaultScreen(display),
                                            query_display,
                                            NV_CTRL_FRONTEND_RESOLUTION,
                                            &resolution);

            if (success == True)
            {
                /* NVCtrl.h indicates the resolution is a 32-bit packed integer
                 * where the upper 16-bits represent the width and the lower 16
                 * bits represent the height.
                 */
                heads[i].resolution.width = (resolution >> 16) & 0xFFFF;
                heads[i].resolution.height = resolution & 0xFFFF;
                i++;
            }
            else
            {
                free(heads);
                return NULL;
            }
        }
    }
#else

   for (i = 0; i < detected; i++)
    {
        heads[i].resolution.width  = screens[i].width;
        heads[i].resolution.height = screens[i].height;
    }

   XFree(screens);
#endif

   *count = detected;
   return heads;
}

void xcd_x11_free_heads(xcd_head_t *heads)
{
    free(heads);
}
